﻿using System.AutoUpdate.Core;
using System.AutoUpdate.Library;
using System.AutoUpdate.Operations;
using System.Collections.Generic;
using System.Reflection;

namespace System.AutoUpdate.Managers
{
    /// <summary>
    /// General update manager, offers interoperability with versions
    /// </summary>
    public class UpdateManager
    {
        #region Singleton

        /// <summary>
        /// Singleton private instance
        /// </summary>
        private static UpdateManager _instance;

        /// <summary>
        /// Singleton public access
        /// </summary>
        public static UpdateManager Instance
        {
            get
            {
                return _instance ?? (_instance = new UpdateManager());
            }
        }

        #endregion

        #region Events
        public event OperationEventHandler UpdateCheckFinished;

        public event OperationEventHandler DownloadStarted;
        public event OperationEventHandler DownloadProgress;
        public event OperationEventHandler DownloadFinished;

        public event OperationEventHandler VerifyCompleted;
        public event OperationEventHandler PrepareCompleted;
        public event OperationEventHandler ApplyStarted;
        public event OperationEventHandler ApplyCompleted;

        public event StatusEventHandler StatusChanged;

        #endregion

        public UpdateStatus Status
        {
            get;
            private set;
        }

        private UpdateManager ()
        {
            Status = UpdateStatus.Idle;
        }

        /// <summary>
        /// Function starts async job to check for updates
        /// </summary>
        public void CheckForUpdates ()
        {
            Status = UpdateStatus.Checking;

            if (StatusChanged != null)
            {
                StatusChanged(this, new StatusEventArgs());
            }

            CheckOperation check = new CheckOperation();
            check.OperationCompleted += (sender, args) =>
                                        {
                                            Status = UpdateStatus.Idle;

                                            if (StatusChanged != null)
                                            {
                                                StatusChanged(this, new StatusEventArgs
                                                                    {
                                                                        Exception = args.Exception
                                                                    });
                                            }

                                            if (args.Exception == null)
                                            {
                                                if (UpdateCheckFinished != null)
                                                {
                                                    UpdateCheckFinished (this, new OperationEventArgs
                                                                               {
                                                                                   UpdateInfo = FilterOnlyNewerVersions (args.UpdateInfo)
                                                                               });
                                                }
                                            }
                                            else
                                            {
                                                if (UpdateCheckFinished != null)
                                                {
                                                    UpdateCheckFinished (this, args);
                                                }
                                            }
                                            
                                        };

            check.StartOperation (null);
        }

        /// <summary>
        /// Function starts downloading updates based on received update info
        /// </summary>
        public void Update (UpdateInfo updateInfo)
        {
            Status = UpdateStatus.Downloading;

            if (StatusChanged != null)
            {
                StatusChanged(this, new StatusEventArgs());
            }

            DownloadOperation download = new DownloadOperation ();

            download.OperationStarted += (sender, args) => DownloadStarted (this, args);
            download.OperationProgressChanged += (sender, args) => DownloadProgress(this, args);

            download.OperationCompleted += (sender, args) =>
                                           {
                                               Status = UpdateStatus.Idle;

                                               if (StatusChanged != null)
                                               {
                                                   StatusChanged(this, new StatusEventArgs
                                                   {
                                                       Exception = args.Exception
                                                   });
                                               }

                                               if (DownloadFinished != null)
                                               {
                                                   DownloadFinished (this, args);
                                               }

                                               //
                                               // If download was a success, continue
                                               //
                                               if (args.Exception == null)
                                               {
                                                   Verify (args.UpdateInfo);
                                               }
                                           };
            //
            // Start download operation
            //

            download.StartOperation (updateInfo);
        }

        /// <summary>
        /// Verify downloaded update
        /// </summary>
        /// <param name="updateInfo"></param>
        private void Verify (UpdateInfo updateInfo)
        {
            Status = UpdateStatus.Verifying;

            if (StatusChanged != null)
            {
                StatusChanged(this, new StatusEventArgs());
            }

            VerifyOperation verify = new VerifyOperation();
            verify.OperationCompleted += (sender, args) =>
                                         {
                                             Status = UpdateStatus.Idle;

                                             if (StatusChanged != null)
                                             {
                                                 StatusChanged(this, new StatusEventArgs
                                                 {
                                                     Exception = args.Exception
                                                 });
                                             }

                                             if (VerifyCompleted != null)
                                             {
                                                 VerifyCompleted (this, args);
                                             }

                                             if (args.Exception == null)
                                             {
                                                 Prepare (args.UpdateInfo);
                                             }
                                         };

            verify.StartOperation (updateInfo);
        }

        /// <summary>
        /// Prepares update to be installed
        /// </summary>
        /// <param name="updateInfo"></param>
        private void Prepare (UpdateInfo updateInfo)
        {
            Status = UpdateStatus.Preparing;

            if (StatusChanged != null)
            {
                StatusChanged(this, new StatusEventArgs());
            }

            PrepareOperation prepare = new PrepareOperation();
            prepare.OperationCompleted += (sender, args) =>
            {
                Status = UpdateStatus.Idle;

                if (StatusChanged != null)
                {
                    StatusChanged(this, new StatusEventArgs
                    {
                        Exception = args.Exception
                    });
                }

                if (PrepareCompleted != null)
                {
                    PrepareCompleted (this, args);
                }

                if (args.Exception == null)
                {
                    Apply(args.UpdateInfo);
                }
            };

            prepare.StartOperation(updateInfo);
        }

        /// <summary>
        /// Prepares update to be installed
        /// </summary>
        /// <param name="updateInfo"></param>
        private void Apply (UpdateInfo updateInfo)
        {
            ApplyOperation apply = new ApplyOperation();
            apply.OperationStarted += (sender, args) =>
                                      {
                                          Status = UpdateStatus.Applying;

                                          if (StatusChanged != null)
                                          {
                                              StatusChanged(this, new StatusEventArgs
                                              {
                                                  Exception = args.Exception
                                              });
                                          }

                                          if (ApplyStarted != null)
                                          {
                                              ApplyStarted (this, args);
                                          }
                                      };
            apply.OperationCompleted += (sender, args) =>
                                        {
                                            Status = UpdateStatus.Idle;

                                            if (StatusChanged != null)
                                            {
                                                StatusChanged(this, new StatusEventArgs
                                                {
                                                    Exception = args.Exception
                                                });
                                            }

                                            if (ApplyCompleted != null)
                                            {
                                                ApplyCompleted (this, args);
                                            }
                                        };

            apply.StartOperation(updateInfo);
        }

        /// <summary>
        /// Function runs clear operation
        /// </summary>
        public void Clean ()
        {
            Status = UpdateStatus.Cleaning;

            if (StatusChanged != null)
            {
                StatusChanged(this, new StatusEventArgs());
            }

            UpdateInfo updateInfo = new UpdateInfo ();

            CleanOperation clean = new CleanOperation();
            clean.StartOperation (updateInfo);

            Status = UpdateStatus.Idle;

            if (StatusChanged != null)
            {
                StatusChanged(this, new StatusEventArgs());
            }
        }

        /// <summary>
        /// Get assembly version
        /// </summary>
        /// <param name="assembly"></param>
        /// <returns></returns>
        public static Version GetCurrentVersion(Assembly assembly = null)
        {
            if (assembly == null)
            {
                assembly = Assembly.GetEntryAssembly();
            }

            return assembly.GetName().Version;
        }

        /// <summary>
        /// Removes old versions from UpdateInfo
        /// </summary>
        /// <param name="info"></param>
        /// <returns></returns>
        public static UpdateInfo FilterOnlyNewerVersions (UpdateInfo info)
        {
            Version current = GetCurrentVersion ();

            //
            // Finds newer versions
            //

            SortedSet<UpdateVersionInfo> versions = new SortedSet<UpdateVersionInfo> ();

            foreach (UpdateVersionInfo versionInfo in info.AvailableVersions)
            {
                if (versionInfo.UpdateVersion > current)
                {
                    versions.Add (versionInfo);
                }
            }

            //
            // Puts newer versions in info
            //

            info.AvailableVersions.Clear ();

            foreach (UpdateVersionInfo versionInfo in versions)
            {
                info.AvailableVersions.Add (versionInfo);
            }

            return info;
        }

        /// <summary>
        /// Returns latest version info object
        /// </summary>
        /// <param name="info"></param>
        /// <returns></returns>
        public static UpdateVersionInfo GetLatestVersion (UpdateInfo info)
        {
            UpdateVersionInfo versionInfo = null;

            foreach (UpdateVersionInfo version in info.AvailableVersions)
            {
                versionInfo = version;

                break;
            }

            return versionInfo;
        }
    }
}
